import sim.engine.*;
import sim.util.Bag;
import sim.util.Int2D;
import sim.util.IntBag;

public class Civilian extends Agent implements Steppable{
	private static final long serialVersionUID = 1L;
	
	private double govtLegitimacy = 0;
	private double grievance = 0;	
	private double riskAversion = 0;
	private double perceivedHardship = 0;
	private double arrestProbability = 0;
	
	public Civilian(Int2D loc, double riskAversion, double perceivedHardship, double govtLegitimacy) {
		super(loc);
		this.riskAversion = riskAversion;
		this.perceivedHardship = perceivedHardship;
		this.govtLegitimacy = govtLegitimacy;
	}
	
	public void step(SimState state){
		EpsteinGrid epState = (EpsteinGrid)state;	
		epState.grid.getNeighborsMaxDistance(loc.x, loc.y, epState.civilianVision, false, neighborsX, neighborsY);
		
		if(jailTerm == 0){
			if(this.active && epState.smartMovement && !grouped && epState.group != 0)
				moveCloser(epState);
			else if((this.active && epState.smartMovement && grouped) || (this.active && epState.smartMovement && epState.group == 0))
				moveCautiously(epState);
			else if(epState.movement)
				move(epState);
			
			setActive(epState);
		}
	}
	
	public void moveCloser(EpsteinGrid epState){
		Bag freeSpaces = new Bag(); // bag of free spaces
		
		//Loop through all agents within vision
		for(int i = 0; i < neighborsX.size(); i++){			
			int x = neighborsX.get(i);
			int y = neighborsY.get(i);	
			
			Bag agents = epState.grid.getObjectsAtLocation(x, y);
			
			//if the space is empty, add it to the bag of free spaces
			if(agents == null){
				freeSpaces.add(new Int2D(x, y));
			}
			//if the space is occupied by agents that are in jail, add it to the bag of free spaces
			else if(agents != null){
				boolean freeSpace = false;
				
				Agent agent;
				for(int j = 0; j < agents.size(); j++){
					agent = (Agent)agents.get(j);
				
					if(agent.jailTerm > 0){
						freeSpace = true;		
					}
					else{
						freeSpace = false;
						break;
					}
				}
				
				if(freeSpace == true){
					freeSpaces.add(new Int2D(x, y));
				}
			}
		}
		
		if(freeSpaces.size() > 0){
			IntBag nX = new IntBag();
			IntBag nY = new IntBag();
			int mostActive = 0;
			Int2D space = null;
			for(Object o : freeSpaces){
				Int2D freeSpace = (Int2D)o;
				epState.grid.getNeighborsMaxDistance(freeSpace.x, freeSpace.y, epState.civilianVision, false, nX, nY);
				
				int activeCount = 0;			
				for(int i = 0; i < nX.size(); i++){			
					int x = nX.get(i);
					int y = nY.get(i);				
					
					Bag ag = epState.grid.getObjectsAtLocation(x, y);
					if(ag != null){
						Agent agent;
						for(int j = 0; j < ag.size(); j++){
							agent = (Agent)ag.get(j);
						
							if(agent.active){
								activeCount++;
							}					
						}
					}
				}
				
				if(activeCount > mostActive){
					mostActive = activeCount;
					space = freeSpace;
				}
				
			}
			
			if(mostActive > 0){
				loc = space;
				epState.grid.setObjectLocation(this, loc);
			}
			else{
				loc = (Int2D) freeSpaces.get(epState.random.nextInt(freeSpaces.size()));
				epState.grid.setObjectLocation(this, loc);
			}
			
			if(mostActive > epState.group * (epState.numCivilians1 + epState.numCivilians2)){
				grouped = true;
			}
			else{
				grouped = false;
			}
		}
	}
	
	public void moveCautiously(EpsteinGrid epState){
		Bag freeSpaces = new Bag(); // bag of free spaces
		
		//Loop through all agents within vision
		for(int i = 0; i < neighborsX.size(); i++){			
			int x = neighborsX.get(i);
			int y = neighborsY.get(i);	
			
			Bag agents = epState.grid.getObjectsAtLocation(x, y);
			
			//if the space is empty, add it to the bag of free spaces
			if(agents == null){
				freeSpaces.add(new Int2D(x, y));
			}
			//if the space is occupied by agents that are in jail, add it to the bag of free spaces
			else if(agents != null){
				boolean freeSpace = false;
				
				Agent agent;
				for(int j = 0; j < agents.size(); j++){
					agent = (Agent)agents.get(j);
				
					if(agent.jailTerm > 0){
						freeSpace = true;		
					}
					else{
						freeSpace = false;
						break;
					}
				}
				
				if(freeSpace == true){
					freeSpaces.add(new Int2D(x, y));
				}
			}
		}
		
		if(freeSpaces.size() > 0){
			IntBag nX = new IntBag();
			IntBag nY = new IntBag();
			int leastCops = 1000;
			Int2D space = null;
			for(Object o : freeSpaces){
				Int2D freeSpace = (Int2D)o;
				epState.grid.getNeighborsMaxDistance(freeSpace.x, freeSpace.y, epState.civilianVision, false, nX, nY);
				
				int copCount = 0;
				
				for(int i = 0; i < nX.size(); i++){			
					int x = nX.get(i);
					int y = nY.get(i);				
					
					Bag ag = epState.grid.getObjectsAtLocation(x, y);
					if(ag != null){
						Agent agent;
						for(int j = 0; j < ag.size(); j++){
							agent = (Agent)ag.get(j);
						
							if(agent instanceof Cop){
								copCount++;
							}
						}
					}
				}
				
				if(copCount < leastCops){
					leastCops = copCount;
					space = freeSpace;
				}				
			}
			
			if(leastCops < 1000){
				loc = space;
				epState.grid.setObjectLocation(this, loc);
			}
		}
		
	}
	
	private void setActive(EpsteinGrid reb) {		
		boolean alreadyActive = false;
		
		if(this.active)
			alreadyActive = true;
		
		int activeV = 0;
		int copsV = 0;
		
		//Loop through all the agents within vision and count the number of active civilians and cops
		for(int i = 0; i < neighborsX.size(); i++){
			int x = neighborsX.get(i);
			int y = neighborsY.get(i);
			Bag agents = reb.grid.getObjectsAtLocation(x, y);
			
			if(agents != null){
				Agent agent;
				for(int j = 0; j < agents.size(); j++){
					agent = (Agent)agents.get(j);
					
					if(agent instanceof Civilian && agent.active){
						activeV++;
					}
					else if(agent instanceof Cop){
						copsV++;
					}
				}
			}		
		}
		
		/*Calculations for going active*/
		arrestProbability = 1 - Math.exp(-2.3*((copsV/(activeV+1))));
		grievance = perceivedHardship * (1 - govtLegitimacy);
		this.active = (grievance - (riskAversion * arrestProbability)) > reb.threshold;
		
		if(!alreadyActive && this.active){
			reb.activeCount++;
			reb.quietCount--;
		}
		else if(alreadyActive && !this.active){
			reb.activeCount--;
			reb.quietCount++;
		}
	}
	
	public double getGrievance(){return perceivedHardship * (1 - govtLegitimacy);}
	public double getRiskAversion(){return riskAversion;}
	public double getPerceivedHardship(){return perceivedHardship;}	
	public double getGovtLegitimacy(){return govtLegitimacy;}
}
